#include "linux/of.h"
#include "linux/err.h"
#include "linux/of_fdt.h"
#include "linux/string.h"
#include "linux/kernel.h"
#include "linux/errno.h"
#include "linux/printk.h"

struct device_node *of_root;
struct device_node *of_chosen;
struct device_node *of_aliases;
struct device_node *of_stdout;
static char *of_stdout_options;

LIST_HEAD(aliases_lookup);

char *of_prop_next_string(struct property *prop, char *cur)
{
    void *curv = cur;

    if (!prop)
        return NULL;

    if (!cur)
        return prop->value;

    curv += strlen(cur) + 1;
    if ((long)curv >= (long)(prop->value) + prop->length)
        return NULL;

    return curv;
}

int of_phandle_iterator_init(struct of_phandle_iterator *it,
                             struct device_node *np,
                             char *list_name,
                             char *cells_name,
                             int cell_count)
{
    uint32_t *list;
    uint32_t size;

    memset(it, 0, sizeof(*it));

    list = of_get_property(np, list_name, &size);
    if (!list)
        return -ENOENT;

    it->cells_name = cells_name;
    it->cell_count = cell_count;
    it->parent = np;
    it->list_end = list + size / sizeof(*list);
    it->phandle_end = list;
    it->cur = list;

    return 0;
}

static void *of_find_property_value_of_size( struct device_node *np,
                                            char *propname, uint32_t min, uint32_t max, size_t *len)
{
    struct property *prop = of_find_property(np, propname, NULL);

    if (!prop)
        return ERR_PTR(-EINVAL);
    if (!prop->value)
        return ERR_PTR(-ENODATA);
    if (prop->length < min)
        return ERR_PTR(-EOVERFLOW);
    if (max && prop->length > max)
        return ERR_PTR(-EOVERFLOW);

    if (len)
        *len = prop->length;

    return prop->value;
}

int of_property_read_variable_u32_array( struct device_node *np,
                                        char *propname, uint32_t *out_values,
                                        size_t sz_min, size_t sz_max)
{
    size_t sz, count;
    uint32_t *val = of_find_property_value_of_size(np, propname,
                                                   (sz_min * sizeof(*out_values)),
                                                   (sz_max * sizeof(*out_values)),
                                                   &sz);

    if (IS_ERR(val))
        return PTR_ERR(val);

    if (!sz_max)
        sz = sz_min;
    else
        sz /= sizeof(*out_values);

    count = sz;
    while (count--)
        *out_values++ = be32_to_cpup(val++);

    return sz;
}

struct property *__of_find_property( struct device_node *np,
                                    char *name, uint32_t *lenp)
{
    struct property *pp;

    if (!np)
        return NULL;

    for (pp = np->properties; pp; pp = pp->next)
    {
        if (of_prop_cmp(pp->name, name) == 0)
        {
            if (lenp)
                *lenp = pp->length;
            break;
        }
    }

    return pp;
}

struct property *of_find_property(struct device_node *np,
                                  char *name,
                                  uint32_t *lenp)
{
    struct property *pp;
    //unsigned long flags;

    // raw_spin_lock_irqsave(&devtree_lock, flags);
    pp = __of_find_property(np, name, lenp);
    // raw_spin_unlock_irqrestore(&devtree_lock, flags);

    return pp;
}

void *__of_get_property( struct device_node *np,
                        char *name, uint32_t *lenp)
{
    struct property *pp = __of_find_property(np, name, lenp);

    return pp ? pp->value : NULL;
}

static int __of_device_is_available( struct device_node *device)
{
    char *status;
    uint32_t statlen;

    if (!device)
        return false;

    status = __of_get_property(device, "status", &statlen);
    if (status == NULL)
        return true;

    if (statlen > 0)
    {
        if (!strcmp(status, "okay") || !strcmp(status, "ok"))
            return true;
    }

    return false;
}

int of_device_is_available( struct device_node *device)
{
    //unsigned long flags;
    int res;

    // raw_spin_lock_irqsave(&devtree_lock, flags);
    res = __of_device_is_available(device);
    // raw_spin_unlock_irqrestore(&devtree_lock, flags);
    return res;
}

int of_property_read_string_helper( struct device_node *np,
                                   char *propname, char **out_strs,
                                   size_t sz, int skip)
{
    struct property *prop = of_find_property(np, propname, NULL);
    int l = 0, i = 0;
    char *p, *end;

    if (!prop)
        return -EINVAL;
    if (!prop->value)
        return -ENODATA;
    p = prop->value;
    end = p + prop->length;

    for (i = 0; p < end && (!out_strs || i < skip + sz); i++, p += l)
    {
        l = strnlen(p, end - p) + 1;
        if (p + l > end)
            return -EILSEQ;
        if (out_strs && i >= skip)
            *out_strs++ = p;
    }
    i -= skip;
    return i <= 0 ? -ENODATA : i;
}

int of_n_addr_cells(struct device_node *np)
{
    uint32_t *ip;

    do
    {
        if (np->parent)
            np = np->parent;
        ip = of_get_property(np, "#address-cells", NULL);
        if (ip)
            return be32_to_cpup(ip);
    } while (np->parent);

    /* No #address-cells property for the root node */
    return 1;
}

int of_n_size_cells(struct device_node *np)
{
    uint32_t *ip;

    do
    {
        if (np->parent)
            np = np->parent;
        ip = of_get_property(np, "#size-cells", NULL);
        if (ip)
            return be32_to_cpup(ip);
    } while (np->parent);
    /* No #size-cells property for the root node */
    return 1;
}

struct device_node *of_get_parent( struct device_node *node)
{
    struct device_node *np;
    //unsigned long flags;

    if (!node)
        return NULL;

    // raw_spin_lock_irqsave(&devtree_lock, flags);
    np = of_node_get(node->parent);
    // raw_spin_unlock_irqrestore(&devtree_lock, flags);
    return np;
}

void *of_get_property(struct device_node *np, char *name, uint32_t *lenp)
{
    struct property *pp = of_find_property(np, name, lenp);

    return pp ? pp->value : NULL;
}

struct device_node *__of_find_all_nodes(struct device_node *prev)
{
    struct device_node *np = NULL;
    if (!prev)
    {
        np = of_root;
    }
    else if (prev->child)
    {
        np = prev->child;
    }
    else
    {
        /* Walk back up looking for a sibling, or the end of the structure */
        np = prev;
        while (np->parent && !np->sibling)
            np = np->parent;
        np = np->sibling; /* Might be null at the end of the tree */
    }
    return np;
}

static struct device_node *__of_get_next_child( struct device_node *node, struct device_node *prev)
{
    struct device_node *next;

    if (!node)
        return NULL;

    next = prev ? prev->sibling : node->child;
    for (; next; next = next->sibling)
        if (of_node_get(next))
            break;
    of_node_put(prev);
    return next;
}

struct device_node *of_get_next_child( struct device_node *node, struct device_node *prev)
{
    struct device_node *next;
    //unsigned long flags;

    // raw_spin_lock_irqsave(&devtree_lock, flags);
    next = __of_get_next_child(node, prev);
    // raw_spin_unlock_irqrestore(&devtree_lock, flags);
    return next;
}

struct device_node *of_get_next_available_child(const struct device_node *node, struct device_node *prev)
{
	struct device_node *next;
	//unsigned long flags;

	if (!node)
		return NULL;

	//raw_spin_lock_irqsave(&devtree_lock, flags);
	next = prev ? prev->sibling : node->child;
    for (; next; next = next->sibling)
    {
        if (!__of_device_is_available(next))
            continue;
        if (of_node_get(next))
            break;
    }
    of_node_put(prev);
    //raw_spin_unlock_irqrestore(&devtree_lock, flags);
	return next;
}

struct device_node *of_find_node_by_type(struct device_node *from, char *type)
{
    struct device_node *np;
    //unsigned long flags;

    // raw_spin_lock_irqsave(&devtree_lock, flags);
    for_each_of_allnodes_from(from, np) if (np->type && (of_node_cmp(np->type, type) == 0) && of_node_get(np)) break;
    of_node_put(from);
    // raw_spin_unlock_irqrestore(&devtree_lock, flags);
    return np;
}

struct device_node *__of_find_node_by_path(struct device_node *parent, char *path)
{
    struct device_node *child;
    int len;

    len = strcspn(path, "/:");
    if (!len)
        return NULL;

    __for_each_child_of_node(parent, child)
    {
        char *name = strrchr(child->full_name, '/');
        // if (WARN(!name, "malformed device_node %s\n", child->full_name))
        if (!name)
            continue;
        name++;
        if (strncmp(path, name, len) == 0 && (strlen(name) == len))
            return child;
    }
    return NULL;
}

struct device_node *of_find_node_opts_by_path( char *path, char **opts)
{
    struct device_node *np = NULL;
    struct property *pp;
    //unsigned long flags;
    char *separator = strchr(path, ':');

    if (opts)
        *opts = separator ? separator + 1 : NULL;

    if (strcmp(path, "/") == 0)
        return of_node_get(of_root);

    /* The path could begin with an alias */
    if (*path != '/')
    {
        int len;
        char *p = separator;

        if (!p)
            p = strchrnul(path, '/');
        len = p - path;

        /* of_aliases must not be NULL */
        if (!of_aliases)
            return NULL;

        for_each_property_of_node(of_aliases, pp)
        {
            if (strlen(pp->name) == len && !strncmp(pp->name, path, len))
            {
                np = of_find_node_by_path(pp->value);
                break;
            }
        }
        if (!np)
            return NULL;
        path = p;
    }

    /* Step down the tree matching path components */
    // raw_spin_lock_irqsave(&devtree_lock, flags);
    if (!np)
        np = of_node_get(of_root);
    while (np && *path == '/')
    {
        path++; /* Increment past '/' delimiter */
        np = __of_find_node_by_path(np, path);
        path = strchrnul(path, '/');
        if (separator && separator < path)
            break;
    }
    // raw_spin_unlock_irqrestore(&devtree_lock, flags);
    return np;
}

struct device_node *of_find_node_by_path( char *path)
{
    return of_find_node_opts_by_path(path, NULL);
}

void of_alias_add(struct alias_prop *ap, struct device_node *np, int id, char *stem, int stem_len)
{
    ap->np = np;
    ap->id = id;
    strncpy(ap->stem, stem, stem_len);
    ap->stem[stem_len] = 0;
    list_add_tail(&ap->link, &aliases_lookup);
    printk("adding DT alias:%s: stem=%s id=%i node=%s\n", ap->alias, ap->stem, ap->id, of_node_full_name(np));
}

/**
 * of_alias_scan - Scan all properties of the 'aliases' node
 *
 * The function scans all the properties of the 'aliases' node and populates
 * the global lookup table with the properties.  It returns the
 * number of alias properties found, or an error code in case of failure.
 *
 * @dt_alloc:	An allocator that provides a virtual address to memory
 *		for storing the resulting tree
 */
void of_alias_scan(void *(*dt_alloc)(uint64_t size, uint64_t align))
{
    struct property *pp;

    of_aliases = of_find_node_by_path("/aliases");
    of_chosen = of_find_node_by_path("/chosen");
    if (of_chosen == NULL)
        of_chosen = of_find_node_by_path("/chosen@0");

    if (of_chosen)
    {
        /* linux,stdout-path and /aliases/stdout are for legacy compatibility */
        char *name = of_get_property(of_chosen, "stdout-path", NULL);
        if (!name)
            name = of_get_property(of_chosen, "linux,stdout-path", NULL);
        // if (IS_ENABLED(CONFIG_PPC) && !name)
        // name = of_get_property(of_aliases, "stdout", NULL);
        if (name)
            of_stdout = of_find_node_opts_by_path(name, &of_stdout_options);
    }

    if (!of_aliases)
        return;

    for_each_property_of_node(of_aliases, pp)
    {
        char *start = pp->name;
        char *end = start + strlen(start);
        struct device_node *np;
        struct alias_prop *ap;
        int id, len;

        /* Skip those we do not want to proceed */
        if (!strcmp(pp->name, "name") ||
            !strcmp(pp->name, "phandle") ||
            !strcmp(pp->name, "linux,phandle"))
            continue;

        np = of_find_node_by_path(pp->value);
        if (!np)
            continue;

        /* walk the alias backwards to extract the id and work out
         * the 'stem' string */
        while (isdigit(*(end - 1)) && end > start)
            end--;
        len = end - start;

        if (kstrtoint(end, 10, &id) < 0)
            continue;

        /* Allocate an alias_prop with enough space for the stem */
        ap = dt_alloc(sizeof(*ap) + len + 1, 4);
        if (!ap)
            continue;
        memset(ap, 0, sizeof(*ap) + len + 1);
        ap->alias = start;
        of_alias_add(ap, np, id, start, len);
    }
}

struct device_node *of_find_node_by_phandle(uint32_t handle)
{
    struct device_node *np;
    //unsigned long flags;

    if (!handle)
        return NULL;

    // raw_spin_lock_irqsave(&devtree_lock, flags);
    for_each_of_allnodes(np) 
        if (np->phandle == handle) 
            break;
    of_node_get(np);
    // raw_spin_unlock_irqrestore(&devtree_lock, flags);
    return np;
}

int of_phandle_iterator_next(struct of_phandle_iterator *it)
{
    uint32_t count = 0;

    if (it->node)
    {
        of_node_put(it->node);
        it->node = NULL;
    }

    if (!it->cur || it->phandle_end >= it->list_end)
        return -ENOENT;

    it->cur = it->phandle_end;

    /* If phandle is 0, then it is an empty entry with no arguments. */
    it->phandle = be32_to_cpup(it->cur++);

    if (it->phandle)
    {

        /*
         * Find the provider node and parse the #*-cells property to
         * determine the argument length.
         */
        it->node = of_find_node_by_phandle(it->phandle);

        if (it->cells_name)
        {
            if (!it->node)
            {
                printk("%s: could not find phandle\n",
                       it->parent->full_name);
                goto err;
            }

            if (of_property_read_u32(it->node, it->cells_name,
                                     &count))
            {
                printk("%s: could not get %s for %s\n",
                       it->parent->full_name,
                       it->cells_name,
                       it->node->full_name);
                goto err;
            }
        }
        else
        {
            count = it->cell_count;
        }

        /*
         * Make sure that the arguments actually fit in the remaining
         * property data length
         */
        if (it->cur + count > it->list_end)
        {
            printk("%s: arguments longer than property\n",
                   it->parent->full_name);
            goto err;
        }
    }

    it->phandle_end = it->cur + count;
    it->cur_count = count;

    return 0;

err:
    if (it->node)
    {
        of_node_put(it->node);
        it->node = NULL;
    }

    return -EINVAL;
}

int of_phandle_iterator_args(struct of_phandle_iterator *it,
                             uint32_t *args,
                             int size)
{
    int i, count;

    count = it->cur_count;

    if ((size < count))
        count = size;

    for (i = 0; i < count; i++)
        args[i] = be32_to_cpup(it->cur++);

    return count;
}

static int __of_parse_phandle_with_args( struct device_node *np,
                                        char *list_name,
                                        char *cells_name,
                                        int cell_count, int index,
                                        struct of_phandle_args *out_args)
{
    struct of_phandle_iterator it;
    int rc, cur_index = 0;

    /* Loop over the phandles until all the requested entry is found */
    of_for_each_phandle(&it, rc, np, list_name, cells_name, cell_count)
    {
        /*
         * All of the error cases bail out of the loop, so at
         * this point, the parsing is successful. If the requested
         * index matches, then fill the out_args structure and return,
         * or return -ENOENT for an empty entry.
         */
        rc = -ENOENT;
        if (cur_index == index)
        {
            if (!it.phandle)
                goto err;

            if (out_args)
            {
                int c;

                c = of_phandle_iterator_args(&it,
                                             out_args->args,
                                             MAX_PHANDLE_ARGS);
                out_args->np = it.node;
                out_args->args_count = c;
            }
            else
            {
                of_node_put(it.node);
            }

            /* Found it! return success */
            return 0;
        }

        cur_index++;
    }

    /*
     * Unlock node before returning result; will be one of:
     * -ENOENT : index is for empty phandle
     * -EINVAL : parsing error on data
     */

err:
    of_node_put(it.node);
    return rc;
}

int of_parse_phandle_with_args( struct device_node *np, char *list_name,
                               char *cells_name, int index,
                               struct of_phandle_args *out_args)
{
    if (index < 0)
        return -EINVAL;

    return __of_parse_phandle_with_args(np, list_name, cells_name, 0, index, out_args);
}


int of_property_match_string(struct device_node *np, char *propname, char *string)
{
	struct property *prop = of_find_property(np, propname, NULL);
	size_t l;
	int i;
	char *p, *end;

	if (!prop)
		return -EINVAL;
	if (!prop->value)
		return -ENODATA;

	p = prop->value;
	end = p + prop->length;

	for (i = 0; p < end; i++, p += l) {
		l = strnlen(p, end - p) + 1;
		if (p + l > end)
			return -EILSEQ;
		printk("comparing %s with %s\n", string, p);
		if (strcmp(string, p) == 0)
			return i; /* Found it; return index */
	}
	return -ENODATA;
}

static int __of_device_is_compatible( struct device_node *device,
                                      char *compat,  char *type,  char *name)
{
    struct property *prop;
     char *cp;
    int index = 0, score = 0;

    /* Compatible match has highest priority */
    if (compat && compat[0])
    {
        prop = __of_find_property(device, "compatible", NULL);
        for (cp = of_prop_next_string(prop, NULL); cp;
             cp = of_prop_next_string(prop, cp), index++)
        {
            if (of_compat_cmp(cp, compat, strlen(compat)) == 0)
            {
                score = INT_MAX / 2 - (index << 2);
                break;
            }
        }
        if (!score)
            return 0;
    }

    /* Matching type is better than matching name */
    if (type && type[0])
    {
        if (!device->type || of_node_cmp(type, device->type))
            return 0;
        score += 2;
    }

    /* Matching name is a bit better than not */
    if (name && name[0])
    {
        if (!device->name || of_node_cmp(name, device->name))
            return 0;
        score++;
    }

    return score;
}

struct device_node *of_find_matching_node_and_match(struct device_node *from,
                                                    struct of_device_id *matches,
                                                    struct of_device_id **match)
{
    struct device_node *np;
     struct of_device_id *m;
//    unsigned long flags;

    if (match)
        *match = NULL;

 //   raw_spin_lock_irqsave(&devtree_lock, flags);
    for_each_of_allnodes_from(from, np)
    {
        m = __of_match_node(matches, np);
        if (m && of_node_get(np))
        {
            if (match)
                *match = m;
            break;
        }
    }
    of_node_put(from);
  //  raw_spin_unlock_irqrestore(&devtree_lock, flags);
    return np;
}